#!/usr/bin/env python3
import os
import subprocess
import sys
from string import Template

STABLE_DIR = "/Volumes/Coding/linux-stable"
MAINLINE_DIR = "/Volumes/Coding/linux-torvalds"

patch_header = """
$inclusion inclusion
from $version
commit $commit
category: bugfix
issue: #$issue
CVE: $cve
Signed-off-by: Tengda Wu <wutengda2@huawei.com>
---------------------------------------
"""

tpl = Template(patch_header)


def get_subject_by_commit(commit, directory):
    ret = subprocess.run(["git", "log", "-1", "--pretty=format:%s", commit], cwd=directory, capture_output=True)
    if ret.returncode != 0:
        print(f"Error: Unable to get subject for commit {commit} in {directory}.")
        sys.exit(ret.returncode)
    return ret.stdout.decode().strip()


def get_commit_by_subject(subject, directory):
    ret = subprocess.run(["git", "log", "--oneline", "--no-abbrev-commit",
                          "--no-merges", "--grep", subject, "-F"], cwd=directory, capture_output=True)
    if ret.returncode != 0:
        print(f"Error: Unable to find commit with subject [{subject}] in {directory}: {ret.stderr.decode()}")
        sys.exit(ret.returncode)
    if ret.stdout.decode().strip() == "":
        print(f"The commit [{subject}] is not found in {directory}")
        return None
    commits = ret.stdout.decode().strip().splitlines()
    fix_by = []
    res = None
    for com_sub in commits:
        if com_sub.startswith("Revert"):
            print(f"Warning: Found a revert patch: {com_sub}")
            continue
        if subject not in com_sub:
            fix_by.append(com_sub)
            continue
        res = com_sub
    if fix_by:
        print(f"Warning: Found multiple post patches fix this commit:")
        for ele in fix_by:
            print(f"  {ele}")
    return res.split()[0]  # Get the commit hash


def parse_version(name_rev):
    # tags/v6.13-rc3~9^2~7^2~6
    # tags/v5.10.238~30
    parts = name_rev.split('~')
    version = parts[0].split('/')[-1] if '/' in parts[0] else parts[0]
    if version == "master":
        print("Warning: The commit is not tagged yet, so inclusion is incorrect, please fill it manually.")
    return version


def get_version(commit, directory):
    ret = subprocess.run(["git", "name-rev", commit], cwd=directory, capture_output=True)
    if ret.returncode != 0:
        return None
    name_rev = ret.stdout.decode().strip()
    return parse_version(name_rev)


def locate_commit(commit):
    subject = get_subject_by_commit(commit, MAINLINE_DIR)
    com_hash = get_commit_by_subject(subject, STABLE_DIR)
    if com_hash:
        return com_hash, STABLE_DIR
    com_hash = get_commit_by_subject(subject, MAINLINE_DIR)
    if com_hash is None:
        raise RuntimeError(f"The subject [{subject}] not found in {MAINLINE_DIR}")
    return com_hash, MAINLINE_DIR


def get_patch_header(commit, issue, cve, directory):
    version = get_version(commit, directory)
    inclusion = "stable" if directory == STABLE_DIR else "mainline"
    version = f"{inclusion}-{version}"
    return tpl.substitute(inclusion=inclusion, version=version, commit=commit, issue=issue, cve=cve)


def get_raw_patch_content(commit, directory):
    ret = subprocess.run(["git", "format-patch", "--stdout", "-1", commit], cwd=directory, capture_output=True)
    if ret.returncode != 0:
        print(f"Error generating patch for commit {commit}: {ret.stderr.decode()}")
        sys.exit(ret.returncode)
    return ret.stdout.decode()


def generate_oh_patch(commit, issue, cve):
    com_hash, directory = locate_commit(commit)
    header = get_patch_header(com_hash, issue, cve, directory)
    raw_content = get_raw_patch_content(com_hash, directory)
    
    meet_first_empty_line = False
    with open(f"{cve}-{commit}.patch", "w") as f:
        for line in raw_content.splitlines():
            if not line.strip() and not meet_first_empty_line:
                meet_first_empty_line = True
                f.write(header + "\n")
            else:
                f.write(line + "\n")
    current_dir = os.path.abspath(os.getcwd())
    patch_path = f"{current_dir}/{cve}-{commit}.patch"
    print(patch_path)
    return patch_path


def main():
    if len(sys.argv) < 4:
        print("Usage: python commit.py <commit> <issue> <CVE>")
        sys.exit(1)
    commit = sys.argv[1].strip()
    issue = sys.argv[2].strip()
    cve = sys.argv[3].strip()
    generate_oh_patch(commit, issue, cve)


if __name__ == "__main__":
    main()
